<html>
    <head>
        <title>动态C# Api 代理  Dynamic C# Api Client Proxies </title>
    </head>
    <body>
        <script>
            //  动态C#API客户端代理   Dynamic C# API Client Proxies


                    /*
                              ABP可以动态创建C#API客户端代理,   来调用您的远程HTTP服务（REST API）。

                              这样，您就不需要处理HttpClient和其他低级细节来调用远程服务并获得结果。


                              动态C#客户端代理会自动为您处理以下内容：

                                     ①、通过考虑HTTP方法、路由、查询字符串参数、请求有效负载和其他细节，将C#方法调用映射到远程服务器HTTP调用。

                                     ②、通过将"访问令牌添（access token）"加到HTTP标头来验证HTTP客户端。

                                     ③、序列化与反序列化 

                                     ④、处理HTTP API版本控制

                                     ⑤、将相关id、当前租户id和当前区域性添加到请求中。

                                     ⑥、正确地处理服务器发送的错误消息并抛出正确的异常。

                             
                              通过使用"动态C#客户端代理"，任何.net 客户端都可以轻松的使用你的 Http Api。
                    */



                    /*
                        一、"静态客户端代理"　与　"动态客户端代理"

                                  ABP 提供了两种类型的"客户端代理生成系统"。

                                  本文档解释了"动态客户端代理"，它在运行时生成客户端代理。

                                  你还可以查看 "静态C#API客户端代理文档"，以了解如何在开发时生成代理。

                                  开发时静态)客户端代理生成具有性能优势，因为它不需要再运行时获取HTTP API的定义。

                                  但是，每当你更新API端点时，你应该重新生成客户端代理代码。

                                  另一方面，动态客户端代理是在运行时生成的，它提供了更简单的开发体验。
                    */



                    /*
                        二、服务接口   Service Interface

                                您的服务/控制器应该实现一个在服务器和客户端之间共享的接口。

                                因此，需要在Application.Contracts 项目中定一个服务接口。

                                eg： 在 Application.Contracts项目中：

                                        public interface IBookAppService : IApplicationService
                                        {
                                            Task<List<BookDto>> GetListAsync();
                                        }

                                (您的接口应该实现自动发现的IRemoteService接口。由于IApplicationService继承了IRemoteService接口，上面的IBookAppService满足这个条件。)

                                在您的服务应用程序中(Application项目中)实现此类。您可以使用autoAPI控制器系统将服务公开为REST API端点
                    */



                    /*
                        三、生成客户端代理  Client Proxy Generation 

                                 (启动模板已经在HttpApi.Client项目中为客户端代理生成预先配置。)    
                                 
                                 如果你没有使用启动模板，请在HttpApi.CLient项目根目录下执行一下命令：

                                            "abp add-package Volo.Abp.Http.Client"
          

                                        1、配置创建动态C#客户端代理：

                                                    现在，它以已经准备好创建客户端代理：

                                                    eg:

                                                        [DependsOn(
                                                                typeof(AbpHttpClientModule), //used to create client proxies
                                                                typeof(BookStoreApplicationContractsModule) //contains the application service interfaces
                                                                )]
                                                            public class MyClientAppModule : AbpModule
                                                            {
                                                                public override void ConfigureServices(ServiceConfigurationContext context)
                                                                {
                                                                    //  创建动态客户端代理  Create dynamic client proxies
                                                                    context.Services.AddHttpClientProxies(
                                                                        typeof(BookStoreApplicationContractsModule).Assembly
                                                                    );
                                                                }
                                                            } 

                                                            AddHttpClientProxies方法获取一个程序集，查找给定程序集中的所有服务接口，创建并注册代理类。

                                        2、端点(端口)配置  Endpoint Configuration  (在Configration系统中配置远程服务端口)

                                                 appsettings.json 文件中的 "RemoteServices"节点 用于获取默认服务地址。
                                                 
                                                 最简单的配置如下所示：

                                                        {
                                                            "RemoteServices": {
                                                                "Default": {
                                                                    "BaseUrl": "http://localhost:53929/"
                                                                } 
                                                            } 
                                                        }

                                                有关详细配置，请参阅下面的“AbpRemoteServiceOptions”部分。
                                
                    */

                    /*
                        四、使用方法  Usage

                                使用起来很简单。只需要在客户端应用程序代码中注入服务接口即可：

                                            // 客户端应用程序代码
                                            public class MyService : ITransientDependency
                                            {
                                                private readonly IBookAppService _bookService;  
                                                // 因为客户端程序应用了 HttpAPI.Client项目，等于间接引用了 Application.Contracts项目。
                                                // 而HttpApi.Client项目中ABP框架自动实现了Application.Contracts项目中定义的接口。
                                                // 所以，服务接口中的每个方法都自动被实现了，会使用HTTPClient调用相应的远程服务。
                                                public MyService(IBookAppService bookService)
                                                {
                                                    _bookService = bookService;
                                                }

                                                public async Task DoIt()
                                                {
                                                    var books = await _bookService.GetListAsync();
                                                    foreach (var book in books)
                                                    {
                                                        Console.WriteLine($"[BOOK {book.Id}] Name={book.Name}");
                                                    }
                                                }
                                            } 

                                此示例注入上面定义的IBookAppService服务接口。每当客户端调用服务方法时，动态客户端代理实现都会进行HTTP调用。



                                ★、IHttpClientProxy接口

                                            虽然您可以像上面那样注入IBookAppService来使用客户端代理，但您可以注入IHttpClientProxy<IBookAppService>以获得更明确的用法。

                                            在这种情况下，您将使用IHttpClientProxy<T>接口的Service属性。    

                    */

                    /*
                        五、配置  Configuration    
                        

                                    1、AbpRemoteServiceOptions选项类

                                                AbpRemoteServiceOptions 默认情况下从 appsetting.json 自动设置。

                                                或者，你可以在模块的 ConfigureServices 方法中配置它以设置或覆盖它。

                                                eg:
                                                        public override void ConfigureServices(ServiceConfigurationContext context)
                                                        {
                                                            context.Services.Configure<AbpRemoteServiceOptions>(options =>
                                                            {
                                                                options.RemoteServices.Default =
                                                                    new RemoteServiceConfiguration("http://localhost:53929/"); // 配置API端点根路径
                                                            });
                                                        }


                                        
                                    2、多个远程服务端点（端口）  

                                                上面的示例配置了"默认"远程服务端点。你可能对不同的服务有不同端点（就像在微服务方法中，每个微服务都有不同的端点）。

                                                在这种情况，你可以将其他端点添加到配置中：

                                                          {
                                                            "RemoteServices": {
                                                                "Default": {
                                                                    "BaseUrl": "http://localhost:53929/"
                                                                },
                                                                "BookStore": {
                                                                    "BaseUrl": "http://localhost:48392/"
                                                                } 
                                                            } 
                                                        }   
                                                            
                                                AddHttpClientProxies 方法可以获取远程服务名称的附加参数：

                                                eg：
                                                    context.Services.AddHttpClientProxies(
                                                        typeof(BookStoreApplicationContractsModule).Assembly,
                                                        remoteServiceConfigurationName: "BookStore"   // 指定服务对应Configuration的节点名称
                                                    );

                                                    remoteServiceConfigurationName参数匹配通过AbpRemoteServiceOptions配置的服务端点。

                                                    如果BookStore端点未定义，则它回退到Default端点。


                                                
                                    3、远程服务配置提供程序  RemoteServiceConfigurationProvider 

                                                    在某些情况下，你可能需要获取特定远程服务的远程服务配置。

                                                    为此，你可以使用  IRemoteServiceConfigurationProvider 接口。

                                                    eg:  获取“BookStore”远程服务的远程服务配置

                                                        public class MyService : ITransientDependency
                                                        {
                                                            private readonly IRemoteServiceConfigurationProvider _remoteServiceConfigurationProvider;

                                                            public MyService(IRemoteServiceConfigurationProvider remoteServiceConfigurationProvider)
                                                            {
                                                                _remoteServiceConfigurationProvider = remoteServiceConfigurationProvider; // 注入远程服务配置提供程序服务
                                                            }

                                                            public async Task GetRemoteServiceConfiguration()
                                                            {
                                                                var configuration = await _remoteServiceConfigurationProvider.GetConfigurationOrDefaultAsync("BookStore"); // 获取远程服务配置
                                                                Console.WriteLine(configuration.BaseUrl);
                                                            }
                                                        }

                                                
                                    4、  asDefaultServices 选项 (作为默认服务实现)

                                                    当您为IBookAppService创建服务代理时，您可以直接注入IBookAppService来使用代理客户端（如使用部分所示）。

                                                    可以将asDefaultServices: false传递给AddHttpClientProxies方法来禁用此功能。

                                                    eg:

                                                         context.Services.AddHttpClientProxies(
                                                                typeof(BookStoreApplicationContractsModule).Assembly,
                                                                asDefaultServices: false
                                                        );

                                                    仅当您的"客户端应用程序" 已经有服务的实现并且您不想被客户端代理覆盖/替换其他实现时，才可能需要使用asDefaultServices: false。

                                                    
                                                  备注： 如果禁用asDefaultServices，则只能使用IHttpClientProxy<T>接口来使用客户端代理。



                                    5、失败重试逻辑 和 Polly集成
                                    
                                                    如果想为客户单端代理在请求失败时添加重试逻辑，可以在模块类的 PreConfigureServices 方法中配置  AbpHttpClientBuilderOptions

                                                    eg: 使用Polly库在失败时重试3次

                                                        public override void PreConfigureServices(ServiceConfigurationContext context)
                                                        {
                                                            PreConfigure<AbpHttpClientBuilderOptions>(options =>
                                                            {
                                                                options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) =>
                                                                {
                                                                    clientBuilder.AddTransientHttpErrorPolicy(policyBuilder =>
                                                                        policyBuilder.WaitAndRetryAsync(
                                                                            3,
                                                                            i => TimeSpan.FromSeconds(Math.Pow(2, i))
                                                                        )
                                                                    );
                                                                });
                                                            });
                                                        }


                                                    此示例使用 "Microsoft. Exents.Http.Polly" 包。
                                                    
                                                    您还需要导入Polly命名空间（using Polly;）才能使用WaitAndRetryAsync方法。
                    */
        </script>
    </body>
</html>